package app

import (
	"errors"
	"log"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/errdefs"
	"github.com/urfave/cli/v2"
)

var RunCommand = &cli.Command{
	Name:      "run",
	Usage:     "Run a container",
	ArgsUsage: "[flags] Image|RootFS ID [COMMAND] [ARG...]",
	Flags: append([]cli.Flag{
		&cli.BoolFlag{
			Name:  "rm",
			Usage: "Remove the container after running, cannot be used with --detach",
		},
		&cli.BoolFlag{
			Name:  "null-io",
			Usage: "Send all IO to /dev/null",
		},
		&cli.StringFlag{
			Name:  "log-uri",
			Usage: "Log uri",
		},
		&cli.BoolFlag{
			Name:    "detach",
			Aliases: []string{"d"},
			Usage:   "Detach from the task after it has started execution, cannot be used with --rm",
		},
		&cli.StringFlag{
			Name:  "fifo-dir",
			Usage: "Directory used for storing IO FIFOs",
		},
		&cli.StringFlag{
			Name:  "cgroup",
			Usage: "Cgroup path (To disable use of cgroup, set to \"\" explicitly)",
		},
	}, append(platformRunFlags, append(SnapshotterFlags, append(RuntimeFlags, ContainerRunFlag...)...)...)...),
	Action: func(context *cli.Context) error {
		var (
			err error
			id  string
			ref string

			rm     = context.Bool("rm")
			detach = context.Bool("detach")
		)
		ref = context.Args().First()
		id = context.Args().Get(1)
		if ref == "" {
			return errors.New("image ref must be provicded")
		}
		if id == "" {
			return errors.New("container id must be provided")
		}
		if rm && detach {
			return errors.New("--rm and --detach cannot be used together")
		}

		client, ctx, cancel, err := NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()

		container, err := NewContainer(ctx, client, context)
		if err != nil {
			return err
		}
		if rm && !detach {
			defer func() {
				if err := container.Delete(ctx, containerd.WithSnapshotCleanup); err != nil {
					log.Fatal(err)
				}
			}()
		}

		var opts []containerd.NewTaskOpts
		if context.Bool("no-pivot") {
			opts = []containerd.NewTaskOpts{containerd.WithNoPivotRoot}
		}
		ioOpts := []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))}
		task, err := NewTask(ctx, client, container, context.Bool("null-io"), ioOpts, opts...)
		if err != nil {
			return err
		}

		var statusC <-chan containerd.ExitStatus
		if !detach {
			defer func() {
				if _, err := task.Delete(ctx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) {
					log.Fatal(err)
				}
			}()

			if statusC, err = task.Wait(ctx); err != nil {
				return err
			}
		}

		if context.IsSet("pid-file") {
			if err := WritePidFile(context.String("pid-file"), int(task.Pid())); err != nil {
				return err
			}
		}
		if err := task.Start(ctx); err != nil {
			return err
		}
		if detach {
			return nil
		}

		sigc := ForwardAllSignals(ctx, task)
		defer StopCatch(sigc)

		status := <-statusC
		code, _, err := status.Result()
		if err != nil {
			return err
		}
		if _, err := task.Delete(ctx); err != nil {
			return err
		}
		if code != 0 {
			return cli.Exit("", int(code))
		}
		return nil
	},
}

var platformRunFlags = []cli.Flag{
	&cli.StringFlag{
		Name:  "uidmap",
		Usage: "Run inside a user namespace with the specified UID mapping range; specified with the format `container-uid:host-uid:length`",
	},
	&cli.StringFlag{
		Name:  "gidmap",
		Usage: "Run inside a user namespace with the specified GID mapping range; specified with the format `container-gid:host-gid:length`",
	},
	&cli.BoolFlag{
		Name:  "remap-labels",
		Usage: "Provide the user namespace ID remapping to the snapshotter via label options; requires snapshotter support",
	},
	&cli.BoolFlag{
		Name:  "privileged-without-host-devices",
		Usage: "Don't pass all host devices to privileged container",
	},
	&cli.Float64Flag{
		Name:  "cpus",
		Usage: "Set the CFS cpu quota",
		Value: 0.0,
	},
	&cli.IntFlag{
		Name:  "cpu-shares",
		Usage: "Set the cpu shares",
		Value: 1024,
	},
	&cli.StringFlag{
		Name:  "cpuset-cpus",
		Usage: "Set the CPUs the container will run in (e.g., 1-2,4)",
	},
	&cli.StringFlag{
		Name:  "cpuset-mems",
		Usage: "Set the memory nodes the container will run in (e.g., 1-2,4)",
	},
}

var RuntimeFlags = []cli.Flag{
	&cli.StringFlag{
		Name:  "runtime",
		Usage: "Runtime name or absolute path to runtime binary",
		Value: DefaultRuntime,
	},
	&cli.StringFlag{
		Name:  "runtime-config-path",
		Usage: "Optional runtime config path",
	},
}

var SnapshotterFlags = []cli.Flag{
	&cli.StringFlag{
		Name:    "snapshotter",
		Usage:   "Snapshotter name. Empty value stands for the default value.",
		EnvVars: []string{"CONTAINERD_SNAPSHOTTER"},
	},
	&cli.StringSliceFlag{
		Name:  "snapshotter-label",
		Usage: "Labels added to the new snapshot for this container.",
	},
}

var ContainerRunFlag = []cli.Flag{
	&cli.StringFlag{
		Name:  "cwd",
		Usage: "Specify the working directory of the process",
	},
	&cli.StringSliceFlag{
		Name:  "env",
		Usage: "Specify additional container environment variables (e.g. FOO=bar)",
	},
	&cli.StringSliceFlag{
		Name:  "label",
		Usage: "Specify additional labels (e.g. foo=bar)",
	},
	&cli.StringSliceFlag{
		Name:  "annotation",
		Usage: "Specify additional OCI annotations (e.g. foo=bar)",
	},
	&cli.StringSliceFlag{
		Name:  "mount",
		Usage: "Specify additional container mount (e.g. type=bind,src=/tmp,dst=/host,options=rbind:ro)",
	},
	&cli.BoolFlag{
		Name:  "net-host",
		Usage: "Enable host networking for the container",
	},
	&cli.BoolFlag{
		Name:  "privileged",
		Usage: "Run privileged container",
	},
	&cli.BoolFlag{
		Name:  "read-only",
		Usage: "Set the containers filesystem as readonly",
	},
	&cli.StringSliceFlag{
		Name:  "with-ns",
		Usage: "Specify existing Linux namespaces to join at container runtime (format '<nstype>:<path>')",
	},
	&cli.StringFlag{
		Name:  "pid-file",
		Usage: "File path to write the task's pid",
	},
	&cli.IntSliceFlag{
		Name:  "gpus",
		Usage: "Add gpus to the container",
	},
	&cli.BoolFlag{
		Name:  "allow-new-privs",
		Usage: "Turn off OCI spec's NoNewPrivileges feature flag",
	},
	&cli.Uint64Flag{
		Name:  "memory-limit",
		Usage: "Memory limit (in bytes) for the container",
	},
	&cli.StringSliceFlag{
		Name:  "cap-add",
		Usage: "Add Linux capabilities (Set capabilities with 'CAP_' prefix)",
	},
	&cli.StringSliceFlag{
		Name:  "cap-drop",
		Usage: "Drop Linux capabilities (Set capabilities with 'CAP_' prefix)",
	},
	&cli.BoolFlag{
		Name:  "seccomp",
		Usage: "Enable the default seccomp profile",
	},
	&cli.StringFlag{
		Name:  "seccomp-profile",
		Usage: "File path to custom seccomp profile. seccomp must be set to true, before using seccomp-profile",
	},
	&cli.StringFlag{
		Name:  "apparmor-default-profile",
		Usage: "Enable AppArmor with the default profile with the specified name, e.g. \"cri-containerd.apparmor.d\"",
	},
	&cli.StringFlag{
		Name:  "apparmor-profile",
		Usage: "Enable AppArmor with an existing custom profile",
	},
	&cli.StringFlag{
		Name:  "hostname",
		Usage: "Set the container's host name",
	},
	&cli.StringFlag{
		Name:    "user",
		Aliases: []string{"u"},
		Usage:   "Username or user id, group optional (format: <name|uid>[:<group|gid>])",
	},
	&cli.BoolFlag{
		Name:  "rootfs",
		Usage: "Use custom rootfs that is not managed by containerd snapshotter",
	},
	&cli.BoolFlag{
		Name:  "no-pivot",
		Usage: "Disable use of pivot-root (linux only)",
	},
	&cli.Int64Flag{
		Name:  "cpu-quota",
		Usage: "Limit CPU CFS quota",
		Value: -1,
	},
	&cli.Uint64Flag{
		Name:  "cpu-period",
		Usage: "Limit CPU CFS period",
	},
	&cli.StringFlag{
		Name:  "rootfs-propagation",
		Usage: "Set the propagation of the container rootfs",
	},
}
